/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#include "pch.h"
#include <baja/graphics/dx11.1/blend_states.hpp>
#include <journal/views/quad_renderer.h>
#include <journal/views/directx/util.hpp>
#include <journal/app/constants.h>

namespace journal { namespace view {

namespace g = baja::graphics;
namespace m = baja::math;
namespace gd = baja::graphics::dx11_1::details;

std::shared_ptr<quad_renderer> quad_renderer::create()
{
    std::shared_ptr<quad_renderer> returnValue = std::make_shared<quad_renderer>();
    return returnValue;
}

std::shared_ptr<quad_renderer> quad_renderer::singleton()
{
    return _singleton;
}

void quad_renderer::render(
    const b::float32 alpha,
    const b::float32 angle,
    const m::point2d targetDimensions,
    const m::point2d dimensions,
    const m::point2d origin,
    const std::shared_ptr<g::itexture>& texture,
    const post_processing_effect ppe,
    const bool writeAlpha,
    const void* context
    )
{
    std::shared_ptr<g::iengine> graphics = constants::graphics();

    this->ensureInitialized(ppe);

    // set up vertex buffer
    quad_vertex* vertex = nullptr;
    _quadVertexBuffer->lockDiscard((b::memory**)&vertex);

    b::float32 dx2 = dimensions.x / 2;
    b::float32 dy2 = dimensions.y / 2;

    // top-left
    vertex->position.x = origin.x - dx2;
    vertex->position.y = origin.y + dy2;
    vertex->uv.x = 0.0f;
    vertex->uv.y = 0.0f;
    vertex++;

    // top-right
    vertex->position.x = origin.x + dx2;
    vertex->position.y = origin.y + dy2;
    vertex->uv.x = 1.0f;
    vertex->uv.y = 0.0f;
    vertex++;

    // bottom-left
    vertex->position.x = origin.x - dx2;
    vertex->position.y = origin.y - dy2;
    vertex->uv.x = 0.0f;
    vertex->uv.y = 1.0f;
    vertex++;

    // bottom-right
    vertex->position.x = origin.x + dx2;
    vertex->position.y = origin.y - dy2;
    vertex->uv.x = 1.0f;
    vertex->uv.y = 1.0f;

    _quadVertexBuffer->unlock();

    constants::graphics()->setTexture(0, texture);

    std::shared_ptr<g::ieffect2> effect;

    if (ppe == post_processing_effect::none)
    {
        effect = _quadEffect;
    }
    else if (ppe == post_processing_effect::horizontal_blur)
    {
        effect = _quadHBlurEffect;
    }
    else if (ppe == post_processing_effect::darken)
    {
        effect = _quadDarkenEffect;
    }
    else if (ppe == post_processing_effect::lasso)
    {
        effect = _lassoEffect;
    }
    else
    {
        effect = _quadVBlurEffect;
    }

    effect->begin(graphics);
    quad_constants c;
    
    // rotation matrix
    c.rotation.x = cos(angle);
    c.rotation.y = -sin(angle);
    c.rotation.z = sin(angle);
    c.rotation.w = cos(angle);
    c.targetDimensions = targetDimensions;
    c.dimensions = dimensions;
    c.origin = origin;
    c.alpha = alpha;

    if (ppe == post_processing_effect::lasso && context)
    {
        // add some offset for the horizontal bar so we don't have two black pixels next to each other on the top-left corner
        bool horizontal = *(static_cast<const bool*>(context));
        c.context.x = horizontal ? 5.f : 0.f;
    }

    effect->setConstantBuffer(L"constants", static_cast<void *>(&c));
    effect->setSamplerState(L"linear_sampler");
    effect->setBlendState(writeAlpha ? L"blend_alpha" : L"blend_alpha_rgb", nullptr, 0xffffffff);

    graphics->setCulling(g::cull_state::none);
    graphics->setZBuffer(g::enabled_state::disabled);
    graphics->setStreamSource(0, _quadVertexBuffer, 0, sizeof(quad_vertex));
    graphics->setVertexDeclaration(_quadVertexDeclaration);

    graphics->drawPrimitive(
        g::primitive_type::triangleStrip,
        0,
        2
        );

    effect->end(graphics);
}

void quad_renderer::ensureInitialized(const post_processing_effect ppe)
{
    std::shared_ptr<g::ieffect2> effect;

    // always initialize this effect
    _quadEffect = this->ensureEffectInitialized(
        L"quad_effect",
        L"quad_pixel_shader.cso",
        L"quad_vertex_shader.cso"
        );
    
    if (ppe == post_processing_effect::horizontal_blur)
    {
        _quadHBlurEffect = this->ensureEffectInitialized(
            L"quad_horizontal_blur_effect",
            L"quad_pixel_shader_h_blur.cso",
            L"quad_vertex_shader.cso"
            );
    }
    else if (ppe == post_processing_effect::vertical_blur)
    {
        _quadVBlurEffect = this->ensureEffectInitialized(
            L"quad_vertical_blur_effect",
            L"quad_pixel_shader_v_blur.cso",
            L"quad_vertex_shader.cso"
            );
    }
    else if (ppe == post_processing_effect::darken)
    {
        _quadDarkenEffect = this->ensureEffectInitialized(
            L"quad_darken_effect",
            L"quad_pixel_shader_darken.cso",
            L"quad_vertex_shader.cso"
            );
    }
    else if (ppe == post_processing_effect::lasso)
    {
        _lassoEffect = this->ensureEffectInitialized(
            L"quad_lasso_effect",
            L"quad_pixel_shader_lasso.cso",
            L"quad_vertex_shader.cso"
            );
    }

    if (!_initialized)
    {
        _quadVertexDeclaration = constants::graphics()->getAssetManager()->getVertexDeclaration(L"quad_vertex_decl", true); // nothrow
        if (!_quadVertexDeclaration)
        {
            _quadVertexDeclaration = constants::graphics()->createVertexDeclaration();
            _quadVertexDeclaration->beginDefinition(2);
            _quadVertexDeclaration->addElement(0, 0, g::vertex_decl_type::float2, g::vertex_decl_usage::position, 0);
            _quadVertexDeclaration->addElement(0, 8, g::vertex_decl_type::float2, g::vertex_decl_usage::texCoord, 0);
            _quadVertexDeclaration->endDefinition(_quadEffect->vertexShaderBytes(), _quadEffect->vertexShaderSize());

            constants::graphics()->getAssetManager()->addVertexDeclaration(L"quad_vertex_decl", _quadVertexDeclaration);
        }

        _quadVertexBuffer = constants::graphics()->createVertexBuffer(
            4 * sizeof(quad_vertex),
            g::vertex_buffer_usage::dynamic,
            constants::graphics()
            );

        _initialized = true;
    }
}

std::shared_ptr<g::ieffect2> quad_renderer::ensureEffectInitialized(
    const b::wchar* name, 
    const b::wchar* pixelShader, 
    const b::wchar* vertexShader
    )
{
    std::shared_ptr<g::ieffect2> effect = constants::graphics()->getAssetManager()->getEffect(name);
    if (effect)
    {
        return effect;
    }

    std::shared_ptr<g::effect2_description> desc = std::make_shared<g::effect2_description>(
        name,
        pixelShader,
        vertexShader
        );

    desc->constantBufferDesc.push_back(
        g::constant_buffer_description(
            L"constants",
            sizeof(quad_constants),
            5));

    desc->blendStateDesc.push_back(g::state_description(L"blend_solid", baja::graphics::dx11_1::createSolidBlendState()));
    desc->blendStateDesc.push_back(g::state_description(L"blend_alpha", baja::graphics::dx11_1::createAlphaBlendState()));
    desc->blendStateDesc.push_back(g::state_description(L"blend_alpha_rgb", baja::graphics::dx11_1::createAlphaBlendState(false)));  // disable writing alpha channel
    desc->samplerStateDesc.push_back(g::state_description(L"linear_sampler", dx11_1::createLinearSampler()));

    desc->textureDesc.push_back(g::texture_description(L"texture1", 0));

    // create effect
    return constants::graphics()->getAssetManager()->addEffect(constants::graphics(), desc);
}

std::shared_ptr<quad_renderer> quad_renderer::_singleton = quad_renderer::create();

}}